查看原文
其他

如何自定义 SpringBoot 多数据源的 starter 组件

SpringForAll 2022-12-01
关注我,回复关键字“spring”
免费领取Spring学习资料

本案例我们使用多数据源封装成一个starter组件,以方便使用多数据源访问数据库的操作

创建一个普通Java项目,引入SpringBoot相关的依赖

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.3</version>
        <relativePath/>
    </parent>
    <groupId>com.gitee.kenewstar.multi.datasource</groupId>
    <artifactId>multi-datasource-spring-boot-starter</artifactId>
    <version>0.0.1</version>
    <name>multi-datasource-spring-boot-starter</name>
    <description>multi-datasource-spring-boot-starter</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
    </dependencies>

</project>

创建常量类和注解

public interface Const {

    String DEFAULT = "default";

    String MULTI_DS = "multiDataSource";

    String CONFIG_PREFIX = "spring.datasource.multi";

}

数据源注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {

    String value() default Const.DEFAULT;

}

创建多数据源属性类

主要用于存储SpringBoot配置文件中配置的数据源属性

@Component
@ConfigurationProperties(prefix = Const.CONFIG_PREFIX)
public class MultiDataSourceProperties {

    private Map<String, DataSourceProp> dataSourcePropMap;

    public Map<String, DataSourceProp> getDataSourcePropMap() {
        return dataSourcePropMap;
    }

    public void setDataSourcePropMap(Map<String, DataSourceProp> dataSourcePropMap) {
        this.dataSourcePropMap = dataSourcePropMap;
    }
}


public class DataSourceProp extends HashMap<String, String> {


}

创建数据源key的切换工具

主要用于设置当前线程下数据源切换时的数据源唯一标识key,以便获取指定的数据源

public class DynamicDataSourceHolder {

    private static final ThreadLocal<String> DATA_SOURCE_THEAD_LOCAL =
            ThreadLocal.withInitial(() -> Const.DEFAULT);


    public static String getDataSource() {
        return DATA_SOURCE_THEAD_LOCAL.get();
    }

    public static void setDataSource(String dataSource) {
        DATA_SOURCE_THEAD_LOCAL.set(dataSource);
    }

    public static void remove() {
        DATA_SOURCE_THEAD_LOCAL.remove();
    }

}

创建多数据源类

创建多数据源类继承AbstractRoutingDataSource类,重写determineCurrentLookupKey()方法,用于获取当前线程中的指定的数据源key,通过该key拿到对应的数据源对象

public class MultiDataSource extends AbstractRoutingDataSource {

    private static final Logger LOGGER = Logger.getLogger(MultiDataSource.class.getName());

    @Override
    protected Object determineCurrentLookupKey() {
        String key = DynamicDataSourceHolder.getDataSource();
        LOGGER.info("DataSource key ---> " + key);
        return key;
    }

}

创建多数据源的切面类

切面类主要用于获取被数据与注解指定的方法,拿到其注解中的属性值,再设置到数据源key设置组件中,方便数据源类获取该key

需使用@Order设置切面优先级,否则设置无效

@Aspect
@Order(100)
public class DynamicDataSourceAdviser {

    @Pointcut("@annotation(com.gitee.kenewstar.multi.datasource.common.DataSource)")
    public void pointcut(){};

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {

        try {
            MethodSignature methodSignature = (MethodSignature) point.getSignature();
            //获取被代理的方法对象
            Method targetMethod = methodSignature.getMethod();
            // 获取数据源注解
            DataSource ds = targetMethod.getAnnotation(DataSource.class);
            if (Objects.nonNull(ds)) {
                DynamicDataSourceHolder.setDataSource(ds.value());
            }
            return point.proceed();
        } finally {
            DynamicDataSourceHolder.remove();
        }


    }

}

创建数据源配置类

@Configuration
@EnableConfigurationProperties(MultiDataSourceProperties.class)
public class MultiDataSourceConfig 
{

    public static final String DS_TYPE = "dsType";

    @Resource
    private MultiDataSourceProperties multiDataSourceProperties;

    private DataSource createDs(DataSourceProp dsProp) {
        DataSource dataSource = null;
        try {
            Class<?> dsClass = Class.forName(dsProp.get(DS_TYPE));
            if (DataSource.class.isAssignableFrom(dsClass)) {
                dataSource = (DataSource) dsClass.getConstructor().newInstance();

                DataSource finalDataSource = dataSource;
                ReflectionUtils.doWithFields(dsClass, field -> {
                    field.setAccessible(true);
                    field.set(finalDataSource, dsProp.get(field.getName()));
                }, field -> {
                    if (Objects.equals(dsProp.get(DS_TYPE), field.getName())) {
                        return false;
                    }
                    return Objects.nonNull(dsProp.get(field.getName()));
                });

            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return dataSource;
    }

    @Bean(Const.MULTI_DS)
    @Primary
    public DataSource multiDataSource() {
        MultiDataSource multiDataSource = new MultiDataSource();

        Map<Object, Object> dataSourceMap = new HashMap<>(multiDataSourceProperties.getDataSourcePropMap().size());
        Map<String, DataSourceProp> dataSourcePropMap = multiDataSourceProperties.getDataSourcePropMap();
        dataSourcePropMap.forEach((lookupKey,dsProp) -> {
            dataSourceMap.put(lookupKey, createDs(dsProp));
        });

        multiDataSource.setTargetDataSources(dataSourceMap);
        multiDataSource.setDefaultTargetDataSource(dataSourceMap.get(Const.DEFAULT));
        return multiDataSource;
    }

    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager(
            @Qualifier(Const.MULTI_DS) DataSource multiDataSource) 
{

        DataSourceTransactionManager tx = new DataSourceTransactionManager();
        tx.setDataSource(multiDataSource);
        return tx;
    }

    @Bean
    public DynamicDataSourceAdviser dynamicDataSourceAdviser() {
        return new DynamicDataSourceAdviser();
    }

}

配置spring.factories文件

在resources目录下创建META-INF目录,在该目录创建spring.factories

文件内容如下:

设置key为开启自动配置的注解全路径名,后面的value值为配置类全路径名,本starter组件中为数据源配置类,如有多个配置类,则以逗号分隔,以反斜杆表示忽略换行

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.gitee.kenewstar.multi.datasource.config.MultiDataSourceConfig

这样我们封装的一个简单的多数据源starter组件就完成了,只需进行maven打包即可在本地使用

maven命令:mvn clean install

使用示例

引入打包后的依赖

<dependency>
    <groupId>com.gitee.kenewstar.multi.datasource</groupId>
    <artifactId>multi-datasource-spring-boot-starter</artifactId>
    <version>0.0.1</version>
</dependency>

修改SpringBoot全局配置文件

default为默认数据源,必须配置, master为可选数据源,名称可自定义。

数据源的属性名称为对应的dsType数据源类型的属性字段

spring:
  datasource:
    multi:
      data-source-prop-map:
        default:
          dsType: com.zaxxer.hikari.HikariDataSource
          jdbcUrl: jdbc:mysql://localhost:3306/test
          username: root
          password: kenewstar
        master:
          dsType: com.zaxxer.hikari.HikariDataSource
          jdbcUrl: jdbc:mysql://localhost:3306/test2
          username: root
          password: kenewstar

使用数据源

直接在指定的方法上添加@DataSource注解即可,注解的默认值为default,数据源的切换通过注解的值进行切换。值为application.yml中配置的default,master等

@Service
public class PersonService {

    @Resource
    private PersonMapper personMapper;

    @DataSource("master")
    @Transactional(rollbackFor = Exception.class)
    public void insertPerson() 
{
        personMapper.insert(new Person(null"kk"12));
        personMapper.insert(new Person(null"kk"12));
    }

}
@Service
public class PersonService {

    @Resource
    private PersonMapper personMapper;

    @DataSource("master")
    @Transactional(rollbackFor = Exception.class)
    public void insertPerson() 
{
        personMapper.insert(new Person(null"kk"12));
        personMapper.insert(new Person(null"kk"12));
    }
}

多数据源starter组件源码地址:

https://gitee.com/kenewstar/multi-datasource-spring-boot-starter

感谢阅读,希望对你有所帮助 :) 

来源:blog.csdn.net/weixin_45840947/article/details/123892165



END



Spring Boot超大文件上传,实现秒传
SpringBoot + Prometheus + Grafana 打造可视化监控一条龙
Spring Framework 6.0 正式GA,新一代框架的开始!
Spring Boot + Redis 实现 API 接口防刷限流

关注后端面试那些事,回复【2022面经】

获取最新大厂Java面经

最后重要提示:高质量的技术交流群,限时免费开放,今年抱团最重要。想进群的,关注SpringForAll社区,回复关键词:加群,拉你进群。

点击这里领取2022大厂面经

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存